#include "rel.h"

/**
 * Handles network functions for reliable protocol application.  
 * I.e., UDP Connection and communication
 *
 * Attributes: 
 * Some code referenced from Professor Edo Biagioni's ICS451 
 * TCP Client/Server code in Excercise 1.
 */

/**
 * Configures network settings for sender and receiver
 * for sender, the port will be 0 and the receiver will supply the
 * port number.
 *
 * @param port portnumber to bind on
 */ 
 
int networkConfigure (unsigned short port){

  // Designate protocol
  struct protoent * protocolEntry;
  if ((protocolEntry = getprotobyname("UDP")) == NULL){
    error("getprotobyname");
  }  
 
  //Socket file descriptor 
  int socketfd;
  
  //Sockaddr pointer to store address and port
  struct sockaddr * sap;
  
  //Sockaddr pointer to store sender's address and port
  struct sockaddr senderSap;
  
  //Create socket for sending and receiving
  if ((socketfd = socket(PF_INET, SOCK_DGRAM, protocolEntry->p_proto)) < 0){
    error("socket");
  }
  
  //Populate sockaddr pointer with address information from command line
  sap = configureHost(port);
  
  //Bind to the given socket and address
  if(bind(socketfd, sap, sizeof(*sap)) != 0) {
    error("bind");
  }
  
  return socketfd;
 }
 
 /**
 * Checks the port number. If successful, returns the port number.
 * If not, returns error message and exits.
 *
 * @param enteredPort - user entered port number
 * @return port - port as an int
 */
int checkPort(char * enteredPort) {

  //Variable to satisfy strol function.  Function sets this
  //pointer to the char after the numerical value`
  char * end;   

  //Convert enteredPort (string) to long integer
  long int tempPortnumber = strtol(enteredPort, &end, 10 ); //Base 10
    
  //Get length of entered argument
  int enteredPortLength = strlen(enteredPort);

  //If argument not numeric, exit
  //strtol points pointer given to where it stopped conversion
  //if no conversion done, it leaves pointer at beginning of string
  if ( enteredPort == end || (enteredPort + enteredPortLength != end) ) {
    error("Port number must be a two-byte short integer");
  }
    
  //If port out of range, exit
  if ( tempPortnumber <= 1024 || tempPortnumber > 65535){
    error("Port number out of range. Port must be between 1024 and 65535");
  }
        
  //If successful assign port number
  else {
    return (int) tempPortnumber;
  }
} // End checkPort

/**
 * Configures the host's sockaddr struct in preparation for the call to bind
 * 
 * @param port - port number to bind on.  For sender, no port is necessary so
 * port number will be 0.
 */
 
struct sockaddr * configureHost (unsigned short port) {
  
  //Structs to hold network information
  static struct sockaddr * sap;
  static struct sockaddr_in sin;
  
  //Ensure struct is empty (all 0s)
  memset (&sin, 0, sizeof(sin));
  
  //Populate struct with port, family, and address to bind on
  sin.sin_family = AF_INET;
  
  // If this is the sender, port will be 0
  sin.sin_port = htons(port);
  
  // Accept all applicable IP addresses
  sin.sin_addr.s_addr = INADDR_ANY;  
  
  //Convert to sockaddr
  sap = (struct sockaddr *) &sin;
  
  return sap;
}


/**
 * Takes a port number and address as string arguments and populates
 * a sockaddr struct for using in network communication
 *
 * @param ip - ip address as a string.  E.g., "127.0.0.1" or "localhost" or "www.google.com"
 * @param portString - port number as a string
 */

struct sockaddr * inputConnectionInfo(char * address, unsigned short port) {
  
  //Struct to load IP address into sockaddr struct
  struct hostent * hostEntry;
  
  //Struct containing address family (e.g., IPv4)
  //also contains port and IP address.  Allocate memory since
  //struct will be returned
  struct sockaddr_in * sin = malloc (sizeof (struct sockaddr_in) );
  
  //Cast to sockaddr struct
  struct sockaddr * sap = (struct sockaddr *) sin;
  
  //Converts IP address to struct sockaddr_in
  hostEntry = gethostbyname(address);
  
  //Check if gethostbyname was successful or not
  if ( (hostEntry == NULL) || (hostEntry->h_addr_list == NULL) ) {
    error ("Invalid address given");
  }
  
  //Ensure that the struct that contains IP address is initialized to 0
  memset ( sin, 0, sizeof(*sin) );
  
  //Populate sockaddr_in struct
  sin->sin_family = AF_INET;
  sin->sin_port = htons(port);
  memcpy(&(sin->sin_addr), hostEntry->h_addr, hostEntry->h_length);
  
  return sap;
} // End inputConnectionInfo

/**
 * Sends a message via UDP
 * 
 * @param address - string containing address (IP or hostname)
 * @param port - port number
 * @param socketfd - socket file descriptor
 * @param msg - msg to send (not necessarily a string)
 * @param msgLen - length of message in bytes
 */
void sendUDP(char * address, unsigned short port, int socketfd, char * msg, int msgLen) {

  //Populate struct with destination port and address
	struct sockaddr * destSockAddr = inputConnectionInfo(address, port);
  
  //Send message to destination address/port
	
	//Error_Send option
	//if (error_send(socketfd, msg, msgLen, 0, destSockAddr, sizeof(*destSockAddr)) < 0){

	if (sendto(socketfd, msg, msgLen, 0, destSockAddr, sizeof(*destSockAddr)) < 0){
	 perror("sendto");
	}
	
	
} // End sendUDP


/**
 * Receives a message via UDP
 * 
 * @param socketfd - socket file descriptor
 * @param buffer - buffer to read information into
 * @param bufferlen - size of buffer
 */
int receiveUDP(int socketfd, unsigned char * buffer, int bufferLen, struct sockaddr * senderSockAddr) {
  
  //Prepare parameter for recvfrom function.  Requires pointer to unsigned int
  unsigned int size = sizeof(* senderSockAddr);
  
  int bytesReceived;
  
  //Set to receive message on bound port and socket
	if((bytesReceived = recvfrom(socketfd, buffer, bufferLen, 0, senderSockAddr, &size)) < 0){
		perror("recvfrom error");
  }
  
  // Lock lastResponseTime
  pthread_mutex_lock ( &mutexLock );
  
  // Reset response time
  lastResponseTime = time(NULL);
  
  // Reset number of retransmits
  numRetransmits = 0;
  
  // Unlock lastResponseTime
  pthread_mutex_unlock ( &mutexLock );
  
  return bytesReceived;
  
} // End receiveUDP

/**
 * Extracts port and IP address from a given sockaddr struct
 *
 * @param senderSockAddr - sockaddr returned from recvfrom function
 * @param addressPort - struct to hold address and port information
 */
void getAddressPort (struct sockaddr * senderSockAddr, struct addressPort * senderAddress) {
   
  //Cast as sockaddr_in - allows for easy access to IP address and port
  struct sockaddr_in * sin = (struct sockaddr_in *) senderSockAddr;
  
  //Assign address and port to struct holding data
  senderAddress->address = ntohl(sin->sin_addr.s_addr);  
  senderAddress->port = ntohs(sin->sin_port);
  
  strcpy(ipAddress,ipToString(senderAddress->address));
  
  if (!sender) {
    senderPort = senderAddress->port;
  }
  else {
    receiverPort = senderAddress->port;
  }

  //DEBUG - Prints address message is received from
  if (debug) {
    printf("\nRECEIVED FROM ADDRESS: %s\n", ipAddress);
    
    if (sender) {
      printf("RECEIVED FROM PORT: %d\n", receiverPort);
    }
    else {
      printf("RECEIVED FROM PORT: %d\n", senderPort);
    }
  }  
} // End getAddressPort

/**
 * Converts a 4-byte IP address to a string
 * 
 * @param address - 4-byte IP address (unsigned int)
 * @return char pointer to string representation of IP address
 */
 
char * ipToString (unsigned int address){
  
  //Create in_addr struct for use with inet_ntoa()
  struct in_addr ia;

  //Populate in_addr with address
  ia.s_addr = htonl(address);
  
  //Return string representation - note: this is for IPv4 only
  return inet_ntoa(ia);
  
} // End ipToString


